// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
define(function (require, exports, module) {
    "use strict";
    var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g
    
    /**
     * Trim `str`.
     */

    function trim(str) {
      return str ? str.replace(/^\s+|\s+$/g, '') : '';
    }

    /**
     * Adds non-enumerable parent node reference to each node.
     */

    function addParent(obj, parent) {
      var isNode = obj && typeof obj.type === 'string';
      var childParent = isNode ? obj : parent;

      for (var k in obj) {
        var value = obj[k];
        if (Array.isArray(value)) {
          value.forEach(function(v) { addParent(v, childParent); });
        } else if (value && typeof value === 'object') {
          addParent(value, childParent);
        }
      }

      if (isNode) {
        Object.defineProperty(obj, 'parent', {
          configurable: true,
          writable: true,
          enumerable: false,
          value: parent || null
        });
      }

      return obj;
    }

    exports.praseCSS = function(css, options){
      options = options || {};

      /**
       * Positional.
       */

      var lineno = 1;
      var column = 1;

      /**
       * Update lineno and column based on `str`.
       */

      function updatePosition(str) {
        var lines = str.match(/\n/g);
        if (lines) lineno += lines.length;
        var i = str.lastIndexOf('\n');
        column = ~i ? str.length - i : column + str.length;
      }

      /**
       * Mark position and patch `node.position`.
       */

      function position() {
        var start = { line: lineno, column: column };
        return function(node){
          node.position = new Position(start);
          whitespace();
          return node;
        };
      }

      /**
       * Store position information for a node
       */

      function Position(start) {
        this.start = start;
        this.end = { line: lineno, column: column };
        this.source = options.source;
      }

      /**
       * Non-enumerable source string
       */

      Position.prototype.content = css;

      /**
       * Error `msg`.
       */

      function error(msg) {
        if (options.silent === true) {
          return false;
        }

        var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
        err.reason = msg;
        err.filename = options.source;
        err.line = lineno;
        err.column = column;
        err.source = css;
        //throw err;
      }

      /**
       * Parse stylesheet.
       */

      function stylesheet() {
        return {
          type: 'stylesheet',
          stylesheet: {
            rules: rules()
          }
        };
      }

      /**
       * Opening brace.
       */

      function open() {
        return match(/^{\s*/);
      }

      /**
       * Closing brace.
       */

      function close() {
        return match(/^}/);
      }

      /**
       * Parse ruleset.
       */

      function rules() {
        var node;
        var rules = [];
        whitespace();
        comments(rules);
        while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
          if (node !== false) {
            rules.push(node);
            comments(rules);
          }
        }
        return rules;
      }

      /**
       * Match `re` and return captures.
       */

      function match(re) {
        var m = re.exec(css);
        if (!m) return;
        var str = m[0];
        updatePosition(str);
        css = css.slice(str.length);
        return m;
      }

      /**
       * Parse whitespace.
       */

      function whitespace() {
        match(/^\s*/);
      }

      /**
       * Parse comments;
       */

      function comments(rules) {
        var c;
        rules = rules || [];
        while (c = comment()) {
          if (c !== false) {
            rules.push(c);
          }
        }
        return rules;
      }

      /**
       * Parse comment.
       */

      function comment() {
        var pos = position();
        if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;

        var i = 2;
        while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i;
        i += 2;

        if ("" === css.charAt(i-1)) {
          return error('End of comment missing');
        }

        var str = css.slice(2, i - 2);
        column += 2;
        updatePosition(str);
        css = css.slice(i);
        column += 2;

        return pos({
          type: 'comment',
          comment: str
        });
      }

      /**
       * Parse selector.
       */

      function selector() {
        var m = match(/^([^{]+)/);
        if (!m) return;
        /* @fix Remove all comments from selectors
         * http://ostermiller.org/findcomment.html */
        return trim(m[0])
          .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
          .replace(/(?:"[^"]*"|'[^']*')/g, function(m) {
            return m.replace(/,/g, '\u200C');
          })
          .split(/\s*(?![^(]*\)),\s*/)
          .map(function(s) {
            return s.replace(/\u200C/g, ',');
          });
      }

      /**
       * Parse declaration.
       */

      function declaration() {
        var pos = position();

        // prop
        var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
        if (!prop) return;
        prop = trim(prop[0]);

        // :
        if (!match(/^:\s*/)) return error("property missing ':'");

        // val
        var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);

        var ret = pos({
          type: 'declaration',
          property: prop.replace(commentre, ''),
          value: val ? trim(val[0]).replace(commentre, '') : ''
        });

        // ;
        match(/^[;\s]*/);

        return ret;
      }

      /**
       * Parse declarations.
       */

      function declarations() {
        var decls = [];

        if (!open()){
            return decls/*error("missing '{'")*/;
        }
          
        comments(decls);

        // declarations
        var decl;
        while (decl = declaration()) {
          if (decl !== false) {
            decls.push(decl);
            comments(decls);
          }
        }

        if (!close()){
            return  decls/*error("missing '}'")*/;
        }
        return decls;
      }

      /**
       * Parse keyframe.
       */

      function keyframe() {
        var m;
        var vals = [];
        var pos = position();

        while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
          vals.push(m[1]);
          match(/^,\s*/);
        }

        if (!vals.length) return;

        return pos({
          type: 'keyframe',
          values: vals,
          declarations: declarations()
        });
      }

      /**
       * Parse keyframes.
       */

      function atkeyframes() {
        var pos = position();
        var m = match(/^@([-\w]+)?keyframes\s*/);

        if (!m) return;
        var vendor = m[1];

        // identifier
        var m = match(/^([-\w]+)\s*/);
        if (!m) return error("@keyframes missing name");
        var name = m[1];

        if (!open()) return error("@keyframes missing '{'");

        var frame;
        var frames = comments();
        while (frame = keyframe()) {
          frames.push(frame);
          frames = frames.concat(comments());
        }

        if (!close()) return error("@keyframes missing '}'");

        return pos({
          type: 'keyframes',
          name: name,
          vendor: vendor,
          keyframes: frames
        });
      }

      /**
       * Parse supports.
       */

      function atsupports() {
        var pos = position();
        var m = match(/^@supports *([^{]+)/);

        if (!m) return;
        var supports = trim(m[1]);

        if (!open()) return error("@supports missing '{'");

        var style = comments().concat(rules());

        if (!close()) return error("@supports missing '}'");

        return pos({
          type: 'supports',
          supports: supports,
          rules: style
        });
      }

      /**
       * Parse host.
       */

      function athost() {
        var pos = position();
        var m = match(/^@host\s*/);

        if (!m) return;

        if (!open()) return error("@host missing '{'");

        var style = comments().concat(rules());

        if (!close()) return error("@host missing '}'");

        return pos({
          type: 'host',
          rules: style
        });
      }

      /**
       * Parse media.
       */

      function atmedia() {
        var pos = position();
        var m = match(/^@media *([^{]+)/);

        if (!m) return;
        var media = trim(m[1]);

        if (!open()) return error("@media missing '{'");

        var style = comments().concat(rules());

        if (!close()) return error("@media missing '}'");

        return pos({
          type: 'media',
          media: media,
          rules: style
        });
      }


      /**
       * Parse custom-media.
       */

      function atcustommedia() {
        var pos = position();
        var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
        if (!m) return;

        return pos({
          type: 'custom-media',
          name: trim(m[1]),
          media: trim(m[2])
        });
      }

      /**
       * Parse paged media.
       */

      function atpage() {
        var pos = position();
        var m = match(/^@page */);
        if (!m) return;

        var sel = selector() || [];

        if (!open()) return error("@page missing '{'");
        var decls = comments();

        // declarations
        var decl;
        while (decl = declaration()) {
          decls.push(decl);
          decls = decls.concat(comments());
        }

        if (!close()) return error("@page missing '}'");

        return pos({
          type: 'page',
          selectors: sel,
          declarations: decls
        });
      }

      /**
       * Parse document.
       */

      function atdocument() {
        var pos = position();
        var m = match(/^@([-\w]+)?document *([^{]+)/);
        if (!m) return;

        var vendor = trim(m[1]);
        var doc = trim(m[2]);

        if (!open()) return error("@document missing '{'");

        var style = comments().concat(rules());

        if (!close()) return error("@document missing '}'");

        return pos({
          type: 'document',
          document: doc,
          vendor: vendor,
          rules: style
        });
      }

      /**
       * Parse font-face.
       */

      function atfontface() {
        var pos = position();
        var m = match(/^@font-face\s*/);
        if (!m) return;

        if (!open()) return error("@font-face missing '{'");
        var decls = comments();

        // declarations
        var decl;
        while (decl = declaration()) {
          decls.push(decl);
          decls = decls.concat(comments());
        }

        if (!close()) return error("@font-face missing '}'");

        return pos({
          type: 'font-face',
          declarations: decls
        });
      }

      /**
       * Parse import
       */

      var atimport = _compileAtrule('import');

      /**
       * Parse charset
       */

      var atcharset = _compileAtrule('charset');

      /**
       * Parse namespace
       */

      var atnamespace = _compileAtrule('namespace');

      /**
       * Parse non-block at-rules
       */


      function _compileAtrule(name) {
        var re = new RegExp('^@' + name + '\\s*([^;]+);');
        return function() {
          var pos = position();
          var m = match(re);
          if (!m) return;
          var ret = { type: name };
          ret[name] = m[1].trim();
          return pos(ret);
        }
      }

      /**
       * Parse at rule.
       */

      function atrule() {
        if (css[0] != '@') return;

        return atkeyframes()
          || atmedia()
          || atcustommedia()
          || atsupports()
          || atimport()
          || atcharset()
          || atnamespace()
          || atdocument()
          || atpage()
          || athost()
          || atfontface();
      }

      /**
       * Parse rule.
       */

      function rule() {
        var pos = position();
        var sel = selector();

        if (!sel) return error('selector missing');
        comments();

        return pos({
          type: 'rule',
          selectors: sel,
          declarations: declarations()
        });
      }

      return addParent(stylesheet());
    };

});